package edu.pku.sei.ocl.evaluation;

import java.io.File;
import java.util.Map.Entry;

import org.eclipse.emf.common.util.BasicEMap;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.impl.EcoreResourceFactoryImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;

import edu.pku.sei.ocl.cst.HelperBlockCS;
import edu.pku.sei.ocl.cst.HelperCallExpCS;
import edu.pku.sei.ocl.cst.HelperDefCS;
import edu.pku.sei.ocl.cst.OCLExpressionCS;
import edu.pku.sei.ocl.cst.SimpleNameCS;
import edu.pku.sei.ocl.cst.VariableCS;
import edu.pku.sei.ocl.types.OclInvalid;
import edu.pku.sei.ocl.types.TypesPackage;
import edu.pku.sei.ocl.utilities.ChangeLog;
import edu.pku.sei.ocl.utilities.OCLConstant;
import edu.pku.sei.ocl.utilities.OCLStandardLibrary;
import edu.pku.sei.ocl.utilities.StandardLibraryUtil;
import edu.pku.sei.ocl.value.OCLBasicCollection;
import edu.pku.sei.ocl.value.OCLCollection;
import edu.pku.sei.ocl.visitor.AbstractOCLVisitor;

public class Environment {
	
	public Environment(){
		globalContext = new Context(this);
//		EcorePackage ecore = EcorePackage.eINSTANCE;
//		name2types.put(ecore.getName(),ecore);
		
		
		resourceSet = new ResourceSetImpl();
		resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
				Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());
		

	}
	
	private Context globalContext;

	public Context getGlobalContext() {
		return globalContext;
	}

	public void setGlobalContext(Context globalContext) {
		this.globalContext = globalContext;
	}
	
	public BasicEMap<String,EPackage> name2types = new BasicEMap<String,EPackage>(5);
	
	public BasicEMap<String,Resource> name2models = new BasicEMap<String,Resource>(5);

	public Object[] searchType(String[] pathName) {
//		if(name2types.isEmpty())
//			return new Object[]{null,pathName[pathName.length-1]};
		
		try {
			if(pathName==null||pathName.length==0)
				return null;
			else if(pathName.length==1){//search all types
				for(Entry<String,EPackage> e : name2types){
					EClassifier type = searchTypeInPackage(e.getValue(),pathName[0]);
					if(type!=null)
						return new Object[]{e.getKey(),type};
				}
				
				return null;
			} else {
				EPackage pkg = name2types.get(pathName[0]);
				if(pkg==null){
//					EClassifier type = searchTypeInPackage(name2types.get(OCLConstant.DEFAULTMODEL),pathName,true);
//					if(type!=null)
//						return new Object[]{OCLConstant.DEFAULTMODEL,type};
//					else return null;
					System.out.println("no type name "+pathName[0]);
					return null;
				} else {
					EClassifier type = searchTypeInPackage(pkg,pathName,false);
					if(type!=null)
						return new Object[]{pathName[0],type};
					else return null;
				}
			}
		} catch (NullPointerException e) {
			System.out.println("no such type:"+pathName);
			System.out.println("return OclAny instead");
			//return new Object[]{OCLConstant.DEFAULTMODEL,TypesPackage.eINSTANCE.getOclAny()};
			return null;
		}
	}
	
	private EClassifier searchTypeInPackage(EPackage pkg,String[] pathName,boolean start){
		int i = start?0:1;
		
		if(i<pathName.length-1){
			if(pkg.getName().equals(pathName[i])==false)
				return null;
			for(i++;i<pathName.length-1;i++){
				for(EPackage spkg : pkg.getESubpackages()){
					boolean flag = false;
					if(spkg.getName().equals(pathName[i])){
						pkg = spkg;
						flag = true;
						break;
					}
					if(flag == false)
						return null;
				}
			}
		}
		return searchTypeInPackage(pkg, pathName[pathName.length-1]);
	}
	
	private EClassifier searchTypeInPackage(EPackage pkg,String name){
		EClassifier cls = pkg.getEClassifier(name);
		
		if(cls==null){
			for(EPackage spkg : pkg.getESubpackages()){
				cls = searchTypeInPackage(spkg,name);
				if(cls!=null)
					return cls;
			}
		}
		
		return cls;
	}
	
	
	private EMap<String,HelperDefCS> helperMap = new BasicEMap<String,HelperDefCS>(10);
	
	
	public void registerHelper(HelperBlockCS helpers){
		for(HelperDefCS hd : helpers.getHelpers()){
			helperMap.put(hd.getHelperName().getValue(), hd);
		}
	}
	public void registerHelper(HelperDefCS helper){
		helperMap.put(helper.getHelperName().getValue(), helper);
	}

	public HelperDefCS getHelper(String name) {
		return helperMap.get(name);
	}
	
	protected Object invokeStaticHelper(HelperCallExpCS callexp,AbstractOCLVisitor visitor, Context context) {
		String helperName = callexp.getHelperName().getValue();
		EList<OCLExpressionCS> arguments = callexp.getArguments();
		Object[] actualParams = new Object[arguments.size()];
		
		for(int i=0;i<actualParams.length;i++) {
			actualParams[i] = arguments.get(i).accept(visitor, context);
		}
		
		try {
			if(callexp.isDefaultAM()==false)
				throw new Exception("when calling static helper, isDefaultAM must be true");
			return invokeStaticHelper(helperName,visitor,context, actualParams);
		} catch (Exception e) {
			e.printStackTrace();
			return OCLConstant.OCLInvalid;
		}
	}
	
	protected Object invokeStaticHelper(String helperName, Object[] actualParams) throws Exception {
		return StandardLibraryUtil.invokeHelperMethod(helperName, this, actualParams);
	}

	public Context createContext(Environment env) {
		return new Context(env);
	}
	
	public Object invokeHelper(String helperName, AbstractOCLVisitor visitor, Context context, Object... params) {
		HelperDefCS def = getHelper(helperName);
		
		if(def==null) {
			Object result = null;
			try {
				result = invokeStaticHelper(helperName, visitor, context, params);
			} catch (Exception e) {
				e.printStackTrace();
				result = OCLConstant.OCLInvalid;
			}
			
			result = OCLStandardLibrary.wrap(result);
			return result;
		} else {
			EList<VariableCS> fParams = def.getParameters();
			
			int i = 0;
			
			for(i=0;i<fParams.size();i++){
				VariableCS var = fParams.get(i);
				OCLVariable v = (OCLVariable) var.accept(visitor, context);
				context.registerVariable(v);
				context.putValue(v, params[i]);
			}
			
			return def.getBody().accept(visitor, context);
		}
	}
	
	protected Object invokeStaticHelper(String helperName,
			AbstractOCLVisitor visitor, Context context,Object...params) {
		try{
			return invokeStaticHelper(helperName, params);
		} catch (Exception e) {
			e.printStackTrace();
			return OCLConstant.OCLInvalid;
		}
	}

	public Object invokeHelper(String helperName, AbstractOCLVisitor visitor, Object... params) {
		return invokeHelper(helperName, visitor, createContext(this),params);
	}
	
	public Object invokeHelper(HelperCallExpCS callexp,AbstractOCLVisitor visitor, Context context,Object... argv){
		HelperDefCS def = getHelper(callexp.getHelperName().getValue());
		
		if(def==null){
			Object result = invokeStaticHelper(callexp,visitor,context);
			result = OCLStandardLibrary.wrap(result);
			
			if(argv.length==0)
				return result;
			else 
				return new Object[]{result,context};
		}
		
		if("relation".equals(def.getHelperModifier())){
			System.out.println("call relation");
		} else if("map".equals(def.getHelperModifier())){
			System.out.println("call relation");
		} else if("rule".equals(def.getHelperModifier())){
			System.out.println("call relation");
		} else if("helper".equals(def.getHelperModifier())
				||"function".equals(def.getHelperModifier())){
			Object result = null;
			Context c = createContext(context.getEnvironment());
			EList<VariableCS> params = def.getParameters();
			EList<OCLExpressionCS> arguments = callexp.getArguments();
			
			int i = 0;
			
			for(i=0;i<params.size();i++){
				VariableCS var = params.get(i);
				OCLVariable v = (OCLVariable) var.accept(visitor, c);
				c.registerVariable(v);
				if(callexp.isDefaultAM()){
					Object value = arguments.get(i).accept(visitor, context);
					c.putValue(v, value);
				}
			}
			
			if(!callexp.isDefaultAM()){
				EList<SimpleNameCS> fpNames = callexp.getFpNames();
				for(i=0;i<fpNames.size();i++){
					Object value = arguments.get(i).accept(visitor, context);
					c.putValue(fpNames.get(i).getValue(), value);
				}
			}
			
			result = def.getBody().accept(visitor, c, argv);
			
			return result;
		}
		
		if(argv.length==0)
			return OclInvalid.invalid;
		else
			return new Object[]{OclInvalid.invalid,context};
	}

	public OCLCollection AllInstances(Object[] typeFromModel) {
		assert typeFromModel[1]!=null;
		
		OCLCollection result = new OCLBasicCollection();
		result.collectionLiteral(true);
		
		if(typeFromModel[1] instanceof EClass){
			if(typeFromModel[0]==null) {
				for(Entry<String,Resource> e : name2models) {
					Resource model = e.getValue();
					AllInstancesInModel((EClass) typeFromModel[1], result, model);
				}
			} else {
				Resource model = name2models.get(typeFromModel[0]);
				assert model!=null;			
				AllInstancesInModel((EClass) typeFromModel[1], result, model);
			}
		} else {
			if(typeFromModel[0]==null) {
				for(Entry<String,Resource> e : name2models) {
					Resource model = e.getValue();
					AllInstancesInModel((String) typeFromModel[1], result, model);
				}
			} else {
				Resource model = name2models.get(typeFromModel[0]);
				assert model!=null;			
				AllInstancesInModel((String) typeFromModel[1], result, model);
			}
		}
		return result;
	}

	protected void AllInstancesInModel(EClass typeFromModel,
			OCLCollection result, Resource model) {
		TreeIterator<EObject> iterator = model.getAllContents();
		while(iterator.hasNext()){
			EObject obj = iterator.next();
			if(typeFromModel.isSuperTypeOf(obj.eClass()))
				result.add(obj);
		}
	}
	
	protected void AllInstancesInModel(String typeFromModel,
			OCLCollection result, Resource model) {
		TreeIterator<EObject> iterator = model.getAllContents();
		while(iterator.hasNext()){
			EObject obj = iterator.next();
			if(obj.eClass().getName().equals(typeFromModel))
				result.add(obj);
		}
	}
	
	private ResourceSet resourceSet;

	static public ChangeLog log = new ChangeLog();
	
	public void loadMetamodel(String path){
		loadMetamodel(OCLConstant.DEFAULTMODEL, path);
	}
	
	public void loadMetamodel(String name,String path){
		loadMetamodel(name,path,true);
	}
	
	public void loadMetamodel(String name, String path, boolean fileSystem) {
		URI mmuri;
		if(fileSystem) {
			try {
				File metamodel = new File(path);
				if(metamodel.exists()) {
					mmuri = URI.createFileURI(metamodel.getAbsolutePath()); // e.g. "c:\.."
				} else {
					mmuri = URI.createURI(path);
				}
			} catch (Exception e) {
				mmuri = URI.createURI(path);
			}
		}
		else {
			mmuri = URI.createPlatformResourceURI(path,true);
		}
		
		Resource res = resourceSet.getResource(mmuri, true);
		EPackage cp = ((EPackage)res.getContents().get(0));
		resourceSet.getPackageRegistry().put(cp.getNsURI(), cp);
		name2types.put(name,cp);
	}
	
	public void loadModel(String name,String path){
		URI modeluri;
		try {
			File model = new File(path);
			if(model.exists()) {
				System.out.println(model.getAbsolutePath());
				modeluri = URI.createFileURI(model.getAbsolutePath()); // e.g. "c:\.."
			} else {
				modeluri = URI.createPlatformResourceURI(path,true);
			}
		} catch (Exception e) {
			modeluri = URI.createURI(path); // e.g. file:/c:/.. or
			// platform:/resource/MyProjectName/..
		}
		Resource mres = resourceSet.getResource(modeluri,true);
		name2models.put(name,mres);
		getGlobalContext().registerVariable(name);
		getGlobalContext().putValue(name, mres.getContents());
	}
	
	public Resource createModel(String name,String path, boolean fileSystem) {
		URI modeluri;
		if(fileSystem) {
			try {
				File model = new File(path);
				modeluri = URI.createFileURI(model.getAbsolutePath()); // e.g. "c:\.."
			} catch (Exception e) {
				modeluri = URI.createURI(path);
			}
		} else {
			modeluri = URI.createPlatformResourceURI(path,true);
		}
		Resource res = resourceSet.createResource(modeluri);
		name2models.put(name, res);
		return res;
	}

	public EList<EObject> getModel(String name){
		Resource res = name2models.get(name);
		if(res==null)
			return null;
		else if(res.getContents().size()==0)
			return null;
		else return res.getContents();
	}

	public EMap<String, HelperDefCS> getHelperMap() {
		return helperMap;
	}

	public void setHelperMap(EMap<String, HelperDefCS> helperMap) {
		this.helperMap = helperMap;
	}
}
